package algo_lignes_sites.data.pre_trt;

import algo_lignes_sites.data.junction.A_Junction;
import algo_lignes_sites.data.junction.JunctionREF;
import algo_lignes_sites.data.path.A_Path;
import algo_lignes_sites.data.path.PathREF;
import algo_lignes_sites.params.Params_ls;
import commons.logs.Logger;
import commons.params_appli.ParamsAppli;
import commons.spatialindex.simplegrid.SimpleGridSPI;
import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.HashSet;


public class CalculComplexite {


	//**************************************************************************
	//  GESTION DE LA COMPLEXITE ET DE L'ATTENUATION DE MOUVEMENT
	//**************************************************************************
	// Notion 1)
	// Attenuation de repulsion dans les abord de nodes
	//  dépend de la proximité au node (du facteur d'entrainement en fait)
	//  et de la complexité du node : si node peu complexe (sous un seuil), alors on attenue moins la repulsion
	// Il y a egalement un mécanisme plus simple aux abord de points multiples
	//   -> l'atténuation ne dépend que du facteur d'entrainement au point multiple
	// Notion 2)
	// Atténuation de répulsion en zones dense, d'une manière générale (proche d'un node ou non)
	// dépend de la complexité de la zone, valeur calculée dans la junctionREF durant les pretraitements
	// On ne multiplie pas ces 2 facteur si une junction est concernée par les 2 notions
	//**************************************************************************


	//**************************************************************************
	// Notion 1)  complexite des nodes
	//  -> On ne peut pas encore calculer l'attenuation pour les junction, qui va resulter de cette complexité de node
	//     comme c'est le cas pour la notion 2, car on aura egalement besoin du facteur d'entrainement du node sur les
	//     junction, notion calculée plus tard sur les junction DER
	//    Le calcul d'atténuation sera fait dans les junction DER au moment du calcul des forces de repulsion
	//    On peut juste precalculer un facteur de réduction d'attenuation pour chaque node, a l'issue du calcul de complexite
	//    apres, le facteur d'attenuation pour une junction donnée dépend des nodes alentours et pt multiple alentours
	//     et de l'entrainement de ces nodes et ptmultiple sur la junction

	// Phase A : complexité initiale des nodes, pour reduction éventuelle de la distance de simplif node
	// Abandonné car ce calcul devait permettre de reduire la zone de simplification des nodes
	// Or il n'existe pas de critère de seuil, basé sur cette complexité, qui conviennent à tous les postes
	//  pour certains, laisser des détails est intéressant, pour d'autres ca rend la généralisation tres moche
	// On abandonne et on opte pour un paramétrage au cas par cas, par l'utilisateur, des zones d'influence
	//  des postes pour lesquels il souhaite voir le détail proche du poste

	// Phase B : complexité des nodes apres simplif node et simplif topo
	//  -> va servir a atténuer moins les répulsions aux abord du poste, si le node est sufisamment simple
	//   pour permettre par exemple à une double ligne reliant 2 poste de s'écarter suffisament pour etre visibles
	static public void computeComplexiteNodes_apresSimplif(ArrayList<JunctionREF> vNodes, Params_ls params) {
		Logger.log_INFO1("-- Calcul complexité des nodes après simplif --");
		for (JunctionREF node : vNodes)
			computeComplexiteNode_apresSimplif(node, params);
	}
	static private void computeComplexiteNode_apresSimplif(JunctionREF node, Params_ls params) {
		node.addInfoDebug("Calcul complexité après simplif");
		node.nodeData.complexite_apresSimplif = computeComplexite(node);
		node.addInfoDebug(" -> value = " + node.nodeData.complexite_apresSimplif);

		// Si complexité sous un seuil, alors on réduit l'influence du node
		//  ce qui limitera l'effet de l'atténuation des mouvements et de l'entrainement proches du node
		// Si seuil = 0 , alors désactivation de cette reduction d'influence
		//  plus le seuil est élevé, plus la réduction d'influence concerne de poste et plus elle est forte
		if (params.seuilPourReductionInfluenceNodeSimple >0 &&
				node.nodeData.complexite_apresSimplif <= params.seuilPourReductionInfluenceNodeSimple) {
			node.nodeData.factReductionInfluenceNode = node.nodeData.complexite_apresSimplif / params.seuilPourReductionInfluenceNodeSimple;
		} else {
			node.nodeData.factReductionInfluenceNode = 1;
		}
		node.addInfoDebug(" -> reduction attenuation repuls = " + node.nodeData.factReductionInfluenceNode);
	}


	static private double computeComplexite(JunctionREF node) {
		double complexite=0;

		// 1ere partie de l'indicateur = nombre de pt multiple dans la zone d'influence (pondérés)
		double rayon_final = node.getRayonNodeFinal();
		double influence_totale = rayon_final * node.nodeData.contrainteGeom.zit;
		double influence_nulle = rayon_final * node.nodeData.contrainteGeom.zit * node.nodeData.contrainteGeom.zin;

		THashSet<A_Junction> setPtMultiple = getAllPtMultipleDansInfluenceNode(node, influence_nulle);
		node.addInfoDebug(" -> nb Pt Mutiple ds influence = " + setPtMultiple.size());

		for (A_Junction ptMult : setPtMultiple) {
			double distNode = node.distTo(ptMult);
			double ponderation;
			if (distNode <= influence_totale)
				ponderation = 1;
			else if (distNode <= influence_nulle) {
				// entre les 2 types de zone d'influence, on pondere par la distance
				// d'abord pondération linéaire
				ponderation = (influence_nulle - distNode) / (influence_nulle - influence_totale);
				// infléchissement de la courbe de décroissance de ponderation, car la gêne occasionnée par un pt multiple diminue en carré de la distance au node
				//ponderation = ponderation*ponderation;
				// -> en fait non car ca rend des postes trop peu complexes : ils ont des intersect loin, certes, mais elles se gênent aussi entres elles ! ex nord de DAX
			} else
				ponderation = 0;
			complexite += ponderation;
		}

		// 2eme partie de la complexité : nb de path qui partent du node
		complexite += 0.5 * Math.max((node.getVPathContenant().size()-2), 0); // 0 si aucun path connecté (node isolé)

		return complexite;
	}


	static private THashSet<A_Junction> getAllPtMultipleDansInfluenceNode(JunctionREF node, double distMax) {
		THashSet<A_Junction> setRes = new THashSet<A_Junction>(20);

		THashSet<A_Path> setPathDejaParcourus = new THashSet<A_Path>(20);
		for (A_Path path : node.getVPathContenant())
			getAllPtMultipleDansInfluenceNode_rec(node, setRes, path, distMax, setPathDejaParcourus);

		return setRes;
	}

	// Ressemble à JunctionREF.computeTopoConnexion() dans le principe, mais on se contente
	// de remplir un vecteur de ptMultiple, on ne fait rien sur les path parcourus
	static private void getAllPtMultipleDansInfluenceNode_rec(JunctionREF node, THashSet<A_Junction> setRes, A_Path path, double distMax, THashSet<A_Path> setPathDejaParcourus) {
		if (setPathDejaParcourus.contains(path)) return;
		setPathDejaParcourus.add(path);
		for (A_Junction j : path.getVJunction()) {
			if (node.distTo(j)> distMax)
				continue;
			if (j.isIntersect() || j.isConnect3() || j.isConnect2()) {
				if (!setRes.contains(j)) {
					setRes.add(j);
					// On parcourt tres généreusement pour le calcul d'indicateur de complexité : meme les path traversant les intersect
					// -> En fait on ne prend que les path non traversant, car avant simplif node
					//  les calcul d'intersection ne sont pas encore calculés !
					// du coup en 2eme passe (apres simplif node, ces intersections rendraient la recherche
					//  de pt multiple plus fructueuse (=> complexité plus grande !) si on parcouraient leur path traversant
					for (A_Path path2 : j.getVPathContenant_nonTraversant())
						getAllPtMultipleDansInfluenceNode_rec(node, setRes, path2, distMax, setPathDejaParcourus);
				}
			}
		}
	}


	//**************************************************************************
	//**************************************************************************
	
	// Notion 2)  complexite d'une zone, calculée pour chaque junction
	//  -> permet de calculer une attenuation des le calcul de la complexite

	public static int nbJunctionAttenuee;


	static public int attenueMouvementJunctionInZoneDense(Params_ls params, ArrayList<PathREF> vPathREF, ArrayList<JunctionREF> vJunctionREF, SimpleGridSPI spiJunction) {
		Logger.log_INFO1("-- Atténuation des mouvements des junctions en zone dense --");
		if (params.seuilAttenuationMouvement <= 0) {
			Logger.log_INFO2("  -> désactivé : seuil <=0");
			return 0;
		}
		Logger.log_INFO2(" -> seuil de déclenchement d'atténuation = " + params.seuilAttenuationMouvement);


		nbJunctionAttenuee = 0;

		// Dans ce SPI, on ne va stocker que les points multiples, car computeComplexiteForJunction()
		//  n'a besoin que d'eux pour le calcul de complexité
		// Le spi sera surdimensionné, mais ce n'est pas tres genant (ca reste plus performant que  d'en recreer un specifique)
		spiJunction.clear();
		for (JunctionREF j : vJunctionREF)
			if (j.isPtMultiple())
				spiJunction.addObject(j);
		
		computeComplexiteForJunctions(params, vPathREF, vJunctionREF, spiJunction);

		attenueMouvementInZoneDense(params, vJunctionREF);

		return nbJunctionAttenuee;
	}


	// Cette fonction utile peut etre appelée depuis le module de simplification topologique => NON EN FAIT
	// spiJunction doit etre mis à jour par l'appelant !
	private static void computeComplexiteForJunctions(Params_ls params, ArrayList<PathREF> vPathREF, ArrayList<JunctionREF> vJunctionREF, SimpleGridSPI spiJunction) {

		// Petite reinit car ce traitement peut etre appelé plusieurs fois
		for (JunctionREF j : vJunctionREF)
			j.complexiteZone = Double.NEGATIVE_INFINITY;

		// Tout d'abord, on doit calculer la taille des junctions à l'échelle finale
		for (PathREF pathREF : vPathREF)
			pathREF.majContrainteGeom_echelleCouranteChanged(1/params.echelleCible);
		for (JunctionREF jREF : vJunctionREF)
			jREF.majContrainteGeom_echelleCouranteChanged(1/params.echelleCible);

		// Puis on calcule la complexité
		double facteurTailleZoneRecherche = ParamsAppli.getParam_d("LigneSite.FACTEUR_RECHERCHE_POUR_CALCUL_COMPLEXITE");
		double facteurDecroissanceInfluence = ParamsAppli.getParam_d("LigneSite.FACTEUR_DECROISSANCE_INFLUENCE_POUR_CALCUL_COMPLEXITE");

		for (JunctionREF j : vJunctionREF)
			computeComplexiteForJunction(j, facteurTailleZoneRecherche, facteurDecroissanceInfluence, spiJunction);
	}

	// le SPI ne doit contenir que des points multiples !
	private static void computeComplexiteForJunction(JunctionREF j, double facteurTailleZoneRecherche, double facteurDecroissanceInfluence, SimpleGridSPI spiJunction) {
		double rayonJ = j.diametre_reel_m/2;
		double rayonJ_larg = j.diametre_reel_m/2 + j.epaisseurDissymetrie_reel_m + j.espaceAutour_reel_m;

		double sommeComplexite = 0;

		ArrayList<JunctionREF> vJProche = spiJunction.getPointsAtDist(j, rayonJ_larg*facteurTailleZoneRecherche, true);

		int nbJProche = vJProche.size();
		for (int i=0; i<nbJProche; i++) {
			JunctionREF jProche = vJProche.get(i);
			assert (jProche.isPtMultiple());
			if (jProche==j) continue;
			
			double rayonJProche = jProche.diametre_reel_m/2;

			double dist = j.distTo(jProche);
			double distLimiteGene = rayonJ + rayonJProche + j.epaisseurDissymetrie_reel_m + jProche.epaisseurDissymetrie_reel_m +
					Math.max(j.espaceAutour_reel_m, jProche.espaceAutour_reel_m);

			double poids = 1;//distLimiteGene/dist;

			double influence;
			if (dist<=distLimiteGene)
				influence=1*poids;
			else
				influence = Math.exp( - facteurDecroissanceInfluence*Math.pow(dist/distLimiteGene - 1, 2)) * poids; //*facteur rend la courbe décroissante plus rapidement

			sommeComplexite += influence;
		}

		j.complexiteZone = sommeComplexite;
	}


	// Il faut avoir calculé la complexité auparavant
	private static void attenueMouvementInZoneDense(Params_ls params, ArrayList<JunctionREF> vJunctionREF) {

		for (JunctionREF j : vJunctionREF) {
			if (j.isNode()) continue;
			assert (j.complexiteZone != Double.NEGATIVE_INFINITY);
			if (j.complexiteZone > params.seuilAttenuationMouvement) {

				j.attenuationMouvement_zoneDense = Math.exp( - Math.pow(j.complexiteZone/params.seuilAttenuationMouvement - 1, 1.2));
				nbJunctionAttenuee++;
			} else
				j.attenuationMouvement_zoneDense = 1; // en deca du seuil, aucune attenuation
		}
	}

}
